#coding:utf8
'''
    模型类
'''
from pymongo import ReturnDocument
from flask import jsonify
from qiniu import Auth, put_file, etag, urlsafe_base64_encode, BucketManager
from PIL import Image, ImageDraw, ImageFont, ImageFilter
from _sys.code import typeDict, responseDict

import pymongo, configparser
import jwt
import re, time
import sys
import traceback
import uuid
import json
import random
import hashlib
import os

class ReadConfig():
    '''
        读取config文件
    :return:
    '''
    def read(self):
        config = configparser.ConfigParser()
        config.read('./config.ini')
        host = config.get('mongodb', 'host')
        port = config.get('mongodb', 'port')
        dbName = config.get('mongodb', 'db_name')
        admin = config.get('mongodb', 'admin')
        password = config.get('mongodb', 'password')
        token = config.get('token', 'str')
        vip_time = config.get('setting', 'vip_time')
        vip_amount = config.get('setting', 'vip_amount')
        send_mail_api_user = config.get('sendcloud', 'send_mail_api_user')
        send_mail_api_key = config.get('sendcloud', 'send_mail_api_key')
        qiniu_ak = config.get('qiniu', 'accessKey')
        qiniu_sk = config.get('qiniu', 'secretKey')
        kplus_helmet_sn20180504cs001 = config.get('qiniu', 'kplus-helmet-sn20180504cs001')
        kplus_website_admin = config.get('qiniu', 'kplus-website-admin')
        web_test = config.get('qiniu', 'web-test')
        admin_ips = config.get('admin', 'admin_ips')
        return { 'host': host, 'port': port, 'db_name': dbName,
                 'admin': admin, 'password': password, 'token': token ,
                 'vip_time': vip_time, 'vip_amount': vip_amount,
                 'send_mail_api_user':send_mail_api_user, 'send_mail_api_key':send_mail_api_key,
                 'qiniu_ak':qiniu_ak, 'qiniu_sk':qiniu_sk, 'kplus-helmet-sn20180504cs001':kplus_helmet_sn20180504cs001,
                 'kplus-website-admin': kplus_website_admin, 'web-test': web_test, 'admin-ips': admin_ips
                 }


class HiddenSensitiveInfo(object):
    '''
        数据信息脱敏处理
    '''
    def __init__(self):
        pass

    def phone(self, sensitive_info):
        n1, n2, n3, *ign, l4, l3, l2, l1 = sensitive_info
        lg = len(sensitive_info) - 7
        return n1+n2+n3+('*'* lg)+l4+l3+l2+l1

    def ID_number(self, sensitive_info):
        n1, *ign, l1 = sensitive_info
        lg = len(sensitive_info) - 2
        return n1 + (lg * '*') + l1

    def bank_card(self, sensitive_info):
        n1,n2,n3,n4,n5,n6,*ign,l4,l3,l2,l1 = sensitive_info
        lg = len(sensitive_info) - 10
        return n1+n2+n3+n4+n5+n6+(lg * '*')+l4+l3+l2+l1

class FormatIO(object):
    '''
        标准化输入输出
    '''
    def timestamp_format(self, timestamp):
        '''
            时间戳标准化 输出dict
        :param timestamp: 时间戳
        :return:
        '''
        if timestamp:  # 必须是时间戳
            localtime = time.localtime(timestamp)
            arg = time.strftime("%Y:%m:%d:%H:%M:%S", localtime).split(':')
            res = {
                'year': arg[0],
                'month': arg[1],
                'day': arg[2],
                'hour': arg[3],
                'minute': arg[4],
                'second': arg[5]
            }
            return res

class Reg(object):
    '''
        正则验证
    '''
    def __init__(self, string):
        self.reg_p = re.compile(r'^0\d{2,3}\d{7,8}$|^1[358]\d{9}$|^147\d{8}')
        self.reg_m = re.compile(r'[^\._][\w\._-]+@(?:[A-Za-z0-9]+\.)+[A-Za-z]+$')

    def _is_phone(self, string):
        _is_ = True if self.reg_p.match(string) else False
        return _is_

    def _is_email(self, string):
        _is_ = True if self.reg_m.match(string) else False
        return _is_

class Singleton(object):
    '''
        单例模型
    '''
    def __new__(cls, *args, **kwargs):
        if not hasattr(cls, '_instance'):
            orig = super(Singleton, cls)
            cls._instance = orig.__new__(cls, *args, **kwargs)
        return cls._instance

class ConnMongoDB(Singleton):
    '''
        连接数据库
    '''
    def __init__(self):
        try:
            MONGODB_CONFIG = ReadConfig().read()
            self.conn = pymongo.MongoClient(
                MONGODB_CONFIG['host'], int(MONGODB_CONFIG['port'])
            )
            self.db = self.conn[MONGODB_CONFIG['db_name']]
            self.username = MONGODB_CONFIG['admin']
            self.password = MONGODB_CONFIG['password']
            if self.username and self.password:
                self.connected = self.db.authenticate(self.username, self.password)
            else:
                self.connected = False
        except Exception:
            print (traceback.format_exc())
            print ('Connect Statics Database Fail.')
            sys.exit(1)

class MongodbOperations(object):
    '''
        操作mongodb 方法集  -- 传统方式
    '''
    def __init__(self):
        self.conn = ConnMongoDB()
        if not self.conn.connected:
            raise NameError('stat:connected Error')

    def find_one(self, table, filter):
        '''
            查询一条数据
        :param table:
        :param filter:
        :return:
        '''
        try:
            result = self.conn.db[table].find_one(filter, projection={'_id': False})
            return result
        except Exception:
            print('bd connect error of find_one')
            return False

    def find_many(self, table, filter, sort=False):
        '''
            查询一组符合条件数据  --排序默认降序
        :param table:
        :param filter:
        :return:  返回类型 list
        '''
        try:
            if sort:
                result = self.conn.db[table].find(filter, projection={'_id': False}).sort(sort, pymongo.DESCENDING)
                return list(result)
            else:
                result = self.conn.db[table].find(filter, projection={'_id': False})
                return list(result)
        except Exception:
            print('bd connect error of find_many')
            return False

    def find_one_and_update(self, table, filter, new_data):
        '''
            查询一条数据并修改
        :param table:
        :param filter:
        :param new_data:
        :return:  修改后的数据
        '''
        try:
            result = self.conn.db[table].find_one_and_update(filter, {'$set': new_data},
                                               projection={'_id': False}, return_document=ReturnDocument.AFTER)
            return result
        except Exception:
            print('bd connect error of find_one_and_update')
            return False

    def find_one_and_delete(self, table, filter):
        '''
            查询一条数据并删除
        :param table:
        :param filter:
        :return:  删除前的数据
        '''
        try:
            result = self.conn.db[table].find_one_and_delete(filter, projection={'_id': False},
                                               return_document=ReturnDocument.BEFORE)
            return result
        except Exception:
            print('bd connect error of find_one_and_delete')
            return False

    def insert_one(self, table, data):
        '''
            插入一条数据并返回 True or False
        :param table:
        :param data:
        :return:
        '''
        try:
            result = self.conn.db[table].insert_one(data)
            return result.acknowledged
        except Exception:
            print('bd connect error of insert_one')
            return False

    def insert_many(self, table, data):
        '''
            插入多组数据
        :param table:
        :param data:  插入数据  类型为list
        :return: True or False
        '''
        try:
            result = self.conn.db[table].insert_many(data)
            return result.acknowledged
        except Exception:
            print('bd connect error of insert_many')
            return False

    def update_one(self, table, filter, data):
        '''
            修改一条数据
        :param table:
        :param filter:
        :param data:
        :return:  True or False
        '''
        try:
            result = self.conn.db[table].update_one(filter, {"$set": data})
            return result.raw_result.get('updatedExisting')
        except Exception:
            print('bd connect error of update_one')
            return False

    def update_many(self, table, filter, data):
        '''
            修改符合条件的多组数据
        :param table:
        :param filter:
        :param data:
        :return:   True or False
        '''
        try:
            result = self.conn.db[table].update_many(filter, {"$set": data})
            return result.acknowledged
        except Exception:
            print('bd connect error of update_many')
            return False

    def delete_one(self, table, filter):
        '''
            删除一条数据
        :param table:
        :param filter:
        :return: True or False
        '''
        try:
            result = self.conn.db[table].delete_one(filter)
            return result.acknowledged
        except Exception:
            print('bd connect error of delete_one')
            return False

    def delete_many(self, table, filter):
        '''
            删除符合筛选项的多条数据
        :param table:
        :param filter:
        :return:
        '''
        try:
            result = self.conn.db[table].delete_many(filter)
            return result.acknowledged
        except Exception:
            print('bd connect error of delete_many')
            return False


class QiniuOperations(object):

    def __init__(self):
        self.config = ReadConfig().read()
        self.q = Auth(self.config.get('qiniu_ak'), self.config.get('qiniu_sk'))

    def upload_barcode(self, code):
        '''
            上传条形码图片
        :param code:
        :return:
        '''
        bucket_name = 'kplus-helmet-sn20180504cs001'  # 上传空间
        key = 'code/barcode/' + code + '.jpg'  # 上传到七牛后保存的文件名
        #print(self.config[bucket_name])
        token = self.q.upload_token(bucket_name, key, 3600)  # 生成上传 Token，可以指定过期时间等
        localfile = './code128of_jpg/' + code + '.jpg'  # 文件本地路径
        ret, info = put_file(token, key, localfile)  # 上传
        link = self.config[bucket_name] + '/' + ret.get('key')
        return link

    def upload_qr_code(self, code):
        '''
            上传二维码图片
        :param code:
        :return:
        '''
        bucket_name = 'kplus-helmet-sn20180504cs001'  # 上传空间
        #print( self.config[bucket_name])
        key = 'code/qrcode/' + code + '.jpg'  # 上传到七牛后保存的文件名
        token = self.q.upload_token(bucket_name, key, 3600)  # 生成上传 Token
        localfile = './qrcodeof_jpg/' + code + '.jpg'  # 文件本地路径
        ret, info = put_file(token, key, localfile)  # 上传
        link = self.config[bucket_name] + '/' + ret.get('key')
        return link

    def get_file_msg(self, bucket_name, file_name):
        '''
            获取文件信息
        :param http:
        :param bucket_name:
        :param file_name:
        :return:
        '''
        bucket = BucketManager(self.q)
        ret, info = bucket.stat(bucket_name, file_name)
        s = json.loads(info.text_body)
        return s

    def upload_img(self, file, hexHash ,file_ext, storage_path):

        f = file
        bucket_name = 'kplus-helmet-sn20180504cs001'  # 上传空间

        save_file_name = hexHash + '.' + file_ext
        save_file_path = './upload_file/website/'+ save_file_name
        key = storage_path + save_file_name  # 上传文件名
        # 查询图片文件是否已经上传存在
        bucket = BucketManager(self.q)
        ret, info = bucket.stat(bucket_name, key)
        if ret == None:
            try:
                f.save(save_file_path)  # 存储文件
                token = self.q.upload_token(bucket_name, key, 3600)  # 生成上传 Token
                localfile = save_file_path  # 文件本地路径
                ret, info = put_file(token, key, localfile)  # 上传
                link = self.config[bucket_name] + '/' + ret.get('key')
                rd = {
                    'code':200,
                    'src': 'http://' + link,
                    'title': key,
                    'hash': ret['hash']
                }
                return rd  # 返回链接
            except Exception:     # 文件存在
                rd = {
                    'code': 400,
                    'msg':'七牛云上传出现错误'
                }
                return rd
        else:
            rd = {
                'code': 200,
                'src': 'http://' + self.config[bucket_name] +'/' + key,
                'title': key,
                'hash': ret['hash']
            }
            return rd  # 返回链接



class ResponseJson(object):
    '''
        返回json报文
    '''
    def __init__(self):
        self.responseDict = responseDict

    def error_response(self, code):
        res = {
            'msg': self.responseDict.get('error').get(code)
        }
        return jsonify(res)

    def correct(self, code, data):
        res = {
            'data': data,
            'msg': self.responseDict.get('correct').get(code)
        }
        return jsonify(res)

class CreateId(object):
    '''
        系统生成各项id
    '''
    def __init__(self):
       pass

    def uid(self, u_mail):
        '''
            生成用户uid 依据为用户邮箱
        :param u_mail:
        :return:
        '''
        if u_mail:
            t = 'kpU_'
            s = ''.join(str(uuid.uuid5(uuid.NAMESPACE_OID, u_mail)).split('-'))
            return t + s

    def pid(self, x, y, z, c):
        '''
            生成商品物理特征id p_id
        :return:
        '''
        t = 'kpP_'
        ctc = x + y + z + c
        s = ''.join(str(uuid.uuid5(uuid.NAMESPACE_OID, ctc)).split('-'))
        return t + s

    def vid(self, title):
        '''
            生成video id 使用视频的md5值作为id 保证了唯一性
        :param http:
        :return:
        '''
        t = 'kpV_'
        #bucket_name = 'kplus-website-vedio'
        #file_name = http[http.find('.com/') + 5:len(http)]
        #info = QiniuOperations().get_file_msg(bucket_name, file_name)
        #s = info['md5']
        s = ''.join(str(uuid.uuid5(uuid.NAMESPACE_OID, title)).split('-'))
        return t + s

    def nid(self):
        '''
            生成news id
        :return:
        '''
        t = 'kpN_'
        s = ''.join(str(uuid.uuid4()).split('-'))
        return t + s

    def agtid(self, name, addr):
        '''
            生成代理 agt id 依据经销商名字地址
        :return:
        '''
        if name and addr:
            t = 'kpAgt_'
            s = ''.join(str(uuid.uuid5(uuid.NAMESPACE_OID, name + addr)).split('-'))
            return t + s


    def oid(self, order_type, num):
        '''
            自增生成订单号  订单编号长度26位
            ####
                在大并发情况下应当考虑使用预先生成订单号桶
                在中并发情况下可考虑使用redis
                在我们的并发量情况下 应当·····如下 16
                类型(34) + 年月日时分秒(20180424172252) + 5位时间戳 + 5位订单量统计(当日)
            ####
        :param order_type:
        :param num:
        :return:
        '''
        if order_type:
            o_t = order_type # 订单类型编码
            t_stamp = time.time()
            n_d = FormatIO().timestamp_format(t_stamp)
            t = ''   # 时间
            for k in n_d:
                t = t + n_d[k]

            ux = ''.join(str(t_stamp).split('.'))[-5:]
            tmp = ['0', '0', '0', '0', '0']  # 最大位数
            arg = list(str(num))
            if len(arg) < len(tmp):
                n = ''.join(tmp[0:len(tmp)-len(arg)] + arg)
                #print(typeDict[o_t], t, ux, n)
                oid = o_t + t + ux + n
                return oid

    def otherid(self):
        s = ''.join(str(uuid.uuid4()).split('-'))
        return s

class Token(object):
    '''
        token 相关
    '''
    def __init__(self):
        self.config = ReadConfig().read()

    def create(self, _id=False):
        '''
            根据uid 创建token
            全站的token有效期均为2小时
            使用jwt模块生成
        :param _id:
        :return:
        '''
        if _id:
            now_time = time.time()
            token = jwt.encode(
                {'_id_': _id, 'overdue_time': now_time + 7200}, self.config['token'], algorithm='HS256')
            return token.decode('utf-8')
        else:
            print('need param _id_')
            return 'error'

    def verify(self, token, _id=False):
        '''
            检验token合法性
        :param _id:
        :return:
        '''
        if _id and token:
            decode_token = jwt.decode(token.encode('utf-8'), self.config['token'], algorithms=['HS256'])
            if _id == decode_token['_id_']:    # id 对应
                if int(time.time()) > decode_token['overdue_time']:
                    return False     # 超时
                return True
            return False
        else:
            print('need param _id_')
            return 'error'


class CreateCodeImg(object):
        '''
            生成验证图片
        '''

        def __init__(self, text_str, size, background):
            '''
            text_str: 验证码显示的字符组成的字符串
            size:  图片大小
            background: 背景颜色
            '''
            self.string = text_str
            self.size = size
            self.background = background

        def create_pic(self):
            '''
            创建一张图片
            '''
            self.width, self.height = self.size
            self.img = Image.new("RGB", self.size, self.background)
            # 实例化画笔
            self.draw = ImageDraw.Draw(self.img)

        def create_point(self):
            '''
            num: 画点的数量
            color: 点的颜色
            功能：画点
            '''
            num = random.randint(5, 10)
            color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
            for i in range(num):
                self.draw.point(
                    (random.randint(0, self.width), random.randint(0, self.height)),
                    fill=color
                )

        def create_line(self):
            '''
            num: 线条的数量
            color: 线条的颜色
            功能：画线条
            '''
            num = random.randint(5, 10)
            color = (random.randint(10, 100), random.randint(40, 140), random.randint(150, 255))
            for i in range(num):
                self.draw.line(
                    [
                        (random.randint(0, self.width), random.randint(0, self.height)),
                        (random.randint(0, self.width), random.randint(0, self.height))
                    ],
                    fill=color
                )

        def create_text(self):
            '''
            font_type: 字体
            font_size: 文字大小
            font_color: 文字颜色
            start_xy:  第一个字左上角坐标,元组类型，如 (5,5)
            功能： 画文字
            '''
            font_type = "./arial.ttf"
            font_size = 32
            start_xy = (random.randint(1, 10), random.randint(1, 10))
            font_color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
            font = ImageFont.truetype(font_type, font_size)
            self.draw.text(start_xy, self.string, font=font, fill=font_color)

        def opera(self):
            '''
            功能：给画出来的线条，文字，扭曲一下，缩放一下，位移一下，滤镜一下。
            就是让它看起来有点歪，有点扭。
            '''
            params = [
                1 - float(random.randint(1, 2)) / 100,
                0,
                0,
                0,
                1 - float(random.randint(1, 10)) / 100,
                float(random.randint(1, 2)) / 500,
                0.001,
                float(random.randint(1, 2)) / 500
            ]
            self.img = self.img.transform(self.size, Image.PERSPECTIVE, params)
            self.img = self.img.filter(ImageFilter.EDGE_ENHANCE_MORE)
















































































